# This file is part of Gehyra.
#
# Gehyra is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Gehyra is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gehyra.  If not, see <http://www.gnu.org/licenses/>.

"""
@package gehyra.config.typedconfig
Support for iniparse object with type parsing.
Uses iniparse to load configuration files, and parses them for correct types.
$Id: typedconfig.py 450 2011-01-20 14:28:47Z andyhhp@gmail.com $
"""

"""
@file gehyra/config/typedconfig.py
Support for iniparse object with type parsing.
Uses iniparse to load configuration files, and parses them for correct types.
"""

from gehyra.common.logger import LOG
from gehyra.common.interfaces import IConfigObject
from gehyra.config.iniconfig import INIConfig, force_type

from pkgutil import get_data
# from ast import literal_eval
from StringIO import StringIO

import os.path
from zope.interface import implements

class TypedDict(dict):
    """Extention of default dictionary.
    Overrides the __setitem__ operator to throw a TypeError if an update would 
    change the type of the stored value.
    """

    def __setitem__(self, key, value):
        """Overrides to throw a TypeError if an update would change the type of
        a value"""
        try:
            i = self.__getitem__(key)
            if(type(i) != type(value)):
                raise TypeError("Attempting to change the type of"\
                                "key %s from %s to %s" % 
                                (key, type(i), type(value)))
            else:
                super(TypedDict, self).__setitem__(key, value)
                #self._iniobj[key] = value;
        except KeyError:
            super(TypedDict, self).__setitem__(key, value)


class TypedINIConfig(dict):
    """INIConfig object with typed data.
    Utilizes a normal INIConfig object and adds support for typed data.
    """
    implements(IConfigObject)

    def __init__(self):
        """Constructor."""

        super(TypedINIConfig, self).__init__()

        ## @brief path to default configuration file
        self.def_path = None
        ## @brief path to configuation file
        self.cfg_path = None
        ## @brief INIConfig object of parsed configuration file
        self.cfg = None

    # pylint: disable=C0301
    # def load_config(self, default, cfg_path):
    #     """Loads configuation from files from INIConfig compatable files.
    #     Evaluates the typed version of the INIConfig data.  First of all, the default
    #     configuration file is loaded and typed.  Then if a non-default file is specified,
    #     that is parsed and typed, updating default values where relevent.  Note that if
    #     conflicting types arise between the default and non-default values, the user will
    #     be warned and the default will take presidence.  This allows the rest of the codebase
    #     to have a guarenteed type for the data.
    #     @param default Path to default configurtion file
    #     @param cfg_path Optional path to non-default configuartion file
    #     """
    # pylint: enable=C0301

    def load_default(self, path):
        """Loads the default configuration.
        Evaluates the type information in the default configuration file, and store
        in self
        @return boolean indicating success or failure"""
        self.def_path = path

        def_str = get_data(__name__, "defaults/"+path)
        if def_str is None:
            LOG.critical("Specified default configuration file '%s' not found"
                         % path)
            return False
        #else:
        #    LOG.debug("Loaded '%s' as default configuration file" % path)

        self.cfg = INIConfig()
        self.cfg.load(StringIO(def_str))
        self.typify()
        
        return True

    def load_real(self, path):
        """Loads the non-default configuration"""

        self.cfg_path = path
        
        # if path is None:
        #     LOG.warn("No configuration path given.  "\
        #     "Using system default (%s)" % self.def_path)
        #     return

        if os.path.exists(path):
            with open(path, "r") as fcfg:
                overrides = INIConfig()
                overrides.load(fcfg)
                self.cfg.update(overrides)
                self.typify()
        else:
            LOG.warn("Configuration file '%s' does not exist.  Using system "\
                     "defaults" % path)



    def typify(self):
        """Populate self.obj with literal_eval'd data from self.cfg.
        Takes the data in self.cfg and evaluates them using ast.literal_eval.
        This data is stored in self.x
        """

        for sname, sect in self.cfg.itersections():
            obj = self[sname] = TypedDict()
            for k, v in sect.iteritems():
                try:
                    obj[k] = force_type(v)
                except TypeError:
                    LOG.warn("User setting '%s' in section '%s' has an "\
                             "invalid type.  Ignoring" % (sname, sect))

    def detypify(self):
        """Takes the objects and converts them back to strings.
        @todo: implement detypify"""

    def save_config(self):
        """Saves the configuration back to its source.
        @todo: implement save_config"""

